﻿using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using VioletDevelopmentLaboratory.Net.Data;
using VioletDevelopmentLaboratory.Net.Web.Configuration;

namespace VioletDevelopmentLaboratory.Net.Web.Component
{
	public enum VoteCreateResult { Success, Exists, Failure }
	public enum VoteDeleteResult { Success, UnknownVote, Failure }
	public enum VoteDoVoteResult { Success, UnknownVote, UnknownAnswer, Already, NoPeriod, Failure }

	public class Vote
	{
		private Vote() { }

		private String applicationName;
		public String ApplicationName { get { return this.applicationName; } }

		private String componentID;
		public String ComponentID { get { return this.componentID; } }

		private String relationID;
		public String RelationID { get { return this.relationID; } }

		private String question;
		public String Question { get { return this.question; } }

		private Boolean isOpen;
		public Boolean IsOpen { get { return this.isOpen; } }

		private Boolean isMultiSelection;
		public Boolean IsMultiSelection { get { return this.isMultiSelection; } }

		private DateTime beginDate;
		public DateTime BeginDate { get { return this.beginDate; } }

		private DateTime endDate;
		public DateTime EndDate { get { return this.endDate; } }

		private Int32 totalVoteCount;
		public Int32 TotalVoteCount { get { return this.totalVoteCount; } }

		private Int32 totalVoterCount;
		public Int32 TotalVoterCount { get { return this.totalVoterCount; } }

		private VoterCollection allVoterCollection;
		public VoterCollection AllVoterCollection { get { return this.IsOpen ? this.allVoterCollection : null; } }

		private VoteAnswerCollection answerCollection;
		public VoteAnswerCollection AnswerCollection { get { return this.answerCollection; } }

		public Boolean IsVotePeriod
		{
			get
			{
				DateTime now = DateTime.Now;

				return now >= this.BeginDate && now <= this.EndDate;
			}
		}

		public Boolean ExistsVoter(String providerUserKey)
		{
			if (providerUserKey == null)
				throw new ArgumentNullException("providerUserKey");

			if (String.IsNullOrEmpty(providerUserKey))
				throw new ArgumentOutOfRangeException("providerUserKey");

			Boolean voterExists = false;

			if (this.allVoterCollection != null)
			{
				foreach (Voter voter in this.allVoterCollection)
				{
					if (voter.ProviderUserKey.ToLower() == providerUserKey.ToLower())
						voterExists = true;
				}
			}

			return voterExists;
		}

		public VoteDoVoteResult DoVote(String answerID, String providerUserKey, String userName)
		{
			if (answerID == null)
				throw new ArgumentNullException("answerID");

			if (providerUserKey == null)
				throw new ArgumentNullException("providerUserKey");

			if (userName == null)
				throw new ArgumentNullException("userName");

			if (String.IsNullOrEmpty(answerID))
				throw new ArgumentOutOfRangeException("answerID");

			if (String.IsNullOrEmpty(providerUserKey))
				throw new ArgumentOutOfRangeException("providerUserKey");

			if (String.IsNullOrEmpty(userName))
				throw new ArgumentOutOfRangeException("userName");

			if (!VUtility.IsGuid(answerID))
				throw new FormatException("answerID must Guid.");

			DateTime now = DateTime.Now;

			if (now < this.BeginDate || now > this.EndDate)
				return VoteDoVoteResult.NoPeriod;

			Object returnValue = null;

			SqlClientAgent.ExecuteNonQuery(
				ConfigurationManager.ConnectionStrings[VWebConfigurationReader.ConnectionStringName].ConnectionString,
				CommandType.StoredProcedure,
				"VWeb_Voter_Create",
				out returnValue,
				new SqlParameter("@ApplicationName", this.ApplicationName),
				new SqlParameter("@ComponentID", this.ComponentID),
				new SqlParameter("@RelationID", this.RelationID),
				new SqlParameter("@AnswerID", answerID),
				new SqlParameter("@ProviderUserKey", providerUserKey),
				new SqlParameter("@UserName", userName)
			);

			switch ((Int32)returnValue)
			{
				case 0:
					Vote last = Vote.GetVote(this.ComponentID, this.relationID);
					this.totalVoteCount = last.TotalVoteCount;
					this.totalVoterCount = last.TotalVoterCount;
					this.allVoterCollection = last.AllVoterCollection;
					this.answerCollection = last.AnswerCollection;

					return VoteDoVoteResult.Success;
				case 1:
					return VoteDoVoteResult.UnknownVote;
				case 2:
					return VoteDoVoteResult.UnknownAnswer;
				case 3:
					return VoteDoVoteResult.Already;
				case 4:
					return VoteDoVoteResult.NoPeriod;
				default:
					return VoteDoVoteResult.Failure;
			}
		}

		public VoteDoVoteResult DoVote(String[] answerIDs, String providerUserKey, String userName)
		{
			if (answerIDs == null)
				throw new ArgumentNullException("answerID");

			if (providerUserKey == null)
				throw new ArgumentNullException("providerUserKey");

			if (userName == null)
				throw new ArgumentNullException("userName");

			if (String.IsNullOrEmpty(providerUserKey))
				throw new ArgumentOutOfRangeException("providerUserKey");

			if (String.IsNullOrEmpty(userName))
				throw new ArgumentOutOfRangeException("userName");

			if (answerIDs.Length < 1)
				throw new ArgumentOutOfRangeException("answerIDs");

			DateTime now = DateTime.Now;

			if (now < this.BeginDate || now > this.EndDate)
				return VoteDoVoteResult.NoPeriod;

			String answerSql = "";

			foreach (String answerID in answerIDs)
			{
				if (!String.IsNullOrEmpty(answerID) && VUtility.IsGuid(answerID))
				{
					answerSql += String.Format("EXEC [VWeb_Voter_Create] @ApplicationName, @ComponentID, @RelationID, '{0}', @ProviderUserKey, @UserName;", answerID);
				}
			}

			if(String.IsNullOrEmpty(answerSql))
				throw new ArgumentOutOfRangeException("answerIDs");

			Object returnValue = null;

			SqlClientAgent.ExecuteNonQuery(
				ConfigurationManager.ConnectionStrings[VWebConfigurationReader.ConnectionStringName].ConnectionString,
				CommandType.Text,
				String.Format("BEGIN TRANSACTION; {0} IF @@ERROR > 0 ROLLBACK; ELSE COMMIT;", answerSql),
				out returnValue,
				new SqlParameter("@ApplicationName", this.ApplicationName),
				new SqlParameter("@ComponentID", this.ComponentID),
				new SqlParameter("@RelationID", this.RelationID),
				new SqlParameter("@ProviderUserKey", providerUserKey),
				new SqlParameter("@UserName", userName)
			);

			switch ((Int32)returnValue)
			{
				case 0:
					Vote last = Vote.GetVote(this.ComponentID, this.relationID);
					this.totalVoteCount = last.TotalVoteCount;
					this.totalVoterCount = last.TotalVoterCount;
					this.allVoterCollection = last.AllVoterCollection;
					this.answerCollection = last.AnswerCollection;

					return VoteDoVoteResult.Success;
				case 1:
					return VoteDoVoteResult.UnknownVote;
				case 2:
					return VoteDoVoteResult.UnknownAnswer;
				case 3:
					return VoteDoVoteResult.Already;
				case 4:
					return VoteDoVoteResult.NoPeriod;
				default:
					return VoteDoVoteResult.Failure;
			}
		}

		public static VoteCreateResult Create(String componentID, String relationID, String question, String[] answers, Boolean isOpen, Boolean isMultiSelection, DateTime beginDate, DateTime endDate)
		{
			if (componentID == null)
				throw new ArgumentNullException("componentID");

			if (relationID == null)
				throw new ArgumentNullException("relationID");

			if (question == null)
				throw new ArgumentNullException("question");

			if (answers == null)
				throw new ArgumentNullException("answers");

			if (String.IsNullOrEmpty(componentID))
				throw new ArgumentOutOfRangeException("componentID");

			if (String.IsNullOrEmpty(relationID))
				throw new ArgumentOutOfRangeException("relationID");

			if (String.IsNullOrEmpty(question))
				throw new ArgumentOutOfRangeException("question");

			if (!VUtility.IsGuid(relationID))
				throw new FormatException("relationID must Guid.");

			if (answers.Length < 1)
				throw new ArgumentOutOfRangeException("answers");

			if (endDate <= beginDate)
				throw new ArgumentOutOfRangeException("beginDate, endDate");

			SqlParameter[] sqlParams = new SqlParameter[9 + (answers.Length * 2)];

			String answerSql = "";

			for(Int32 i = 0; i < answers.Length; i++)
			{
				if(!String.IsNullOrEmpty(answers[i]))
				{
					answerSql += String.Format("EXEC [VWeb_VoteAnswer_Create] @ApplicationName, @ComponentID, @RelationID, @Answer{0}, @Index{0}; ", i);
					sqlParams[i] = new SqlParameter("@Answer" + i, answers[i]);
					sqlParams[answers.Length + i] = new SqlParameter("@Index" + i, i);
				}
			}

			sqlParams[(answers.Length * 2)] = new SqlParameter("@ApplicationName", VWebConfigurationReader.ApplicationName);
			sqlParams[(answers.Length * 2) + 1] = new SqlParameter("@ComponentID", componentID);
			sqlParams[(answers.Length * 2) + 2] = new SqlParameter("@RelationID", relationID);
			sqlParams[(answers.Length * 2) + 3] = new SqlParameter("@Question", question);
			sqlParams[(answers.Length * 2) + 4] = new SqlParameter("@IsOpen", isOpen);
			sqlParams[(answers.Length * 2) + 5] = new SqlParameter("@IsMultiSelection", isMultiSelection);
			sqlParams[(answers.Length * 2) + 6] = new SqlParameter("@BeginDate", beginDate);
			sqlParams[(answers.Length * 2) + 7] = new SqlParameter("@EndDate", endDate);

			SqlParameter createResultParam = new SqlParameter();
			createResultParam.ParameterName = "@CreateResult";
			createResultParam.SqlDbType = SqlDbType.Int;
			createResultParam.Direction = ParameterDirection.Output;

			sqlParams[(answers.Length * 2) + 8] = createResultParam;

			SqlClientAgent.ExecuteNonQuery(
				ConfigurationManager.ConnectionStrings[VWebConfigurationReader.ConnectionStringName].ConnectionString,
				CommandType.Text,
				String.Format("BEGIN TRANSACTION; {0} IF @CreateResult = 0 BEGIN {1} END IF @@ERROR > 0 ROLLBACK; ELSE COMMIT;",
					"EXEC @CreateResult = [VWeb_Vote_Create] @ApplicationName, @ComponentID, @RelationID, @Question, @IsOpen, @IsMultiSelection, @BeginDate, @EndDate;",
					answerSql),
				sqlParams
			);

			switch ((Int32)createResultParam.Value)
			{
				case 0:
					return VoteCreateResult.Success;
				case 1:
					return VoteCreateResult.Exists;
				default:
					return VoteCreateResult.Failure;
			}
		}

		public static VoteDeleteResult Delete(String componentID, String relationID)
		{
			if (componentID == null)
				throw new ArgumentNullException("componentID");

			if (relationID == null)
				throw new ArgumentNullException("relationID");

			if (String.IsNullOrEmpty(componentID))
				throw new ArgumentOutOfRangeException("componentID");

			if (String.IsNullOrEmpty(relationID))
				throw new ArgumentOutOfRangeException("relationID");

			if (!VUtility.IsGuid(relationID))
				throw new FormatException("relationID must Guid.");

			Object returnValue = null;

			SqlClientAgent.ExecuteNonQuery(
				ConfigurationManager.ConnectionStrings[VWebConfigurationReader.ConnectionStringName].ConnectionString,
				CommandType.StoredProcedure,
				"VWeb_Vote_Delete",
				out returnValue,
				new SqlParameter("@ApplicationName", VWebConfigurationReader.ApplicationName),
				new SqlParameter("@ComponentID", componentID),
				new SqlParameter("@RelationID", relationID)
			);

			switch ((Int32)returnValue)
			{
				case 0:
					return VoteDeleteResult.Success;
				case 1:
					return VoteDeleteResult.UnknownVote;
				default:
					return VoteDeleteResult.Failure;
			}
		}

		public static Vote GetVote(String componentID, String relationID)
		{
			if (componentID == null)
				throw new ArgumentNullException("componentID");

			if (relationID == null)
				throw new ArgumentNullException("relationID");

			if (String.IsNullOrEmpty(componentID))
				throw new ArgumentOutOfRangeException("componentID");

			if (String.IsNullOrEmpty(relationID))
				throw new ArgumentOutOfRangeException("relationID");

			if (!VUtility.IsGuid(relationID))
				throw new FormatException("relationID must Guid.");

			DataSet dataSet = SqlClientAgent.ExecuteDataSet(
				ConfigurationManager.ConnectionStrings[VWebConfigurationReader.ConnectionStringName].ConnectionString,
				CommandType.StoredProcedure,
				"VWeb_Vote_GetVote",
				new SqlParameter("@ApplicationName", VWebConfigurationReader.ApplicationName),
				new SqlParameter("@ComponentID", componentID),
				new SqlParameter("@RelationID", relationID)
			);

			return fillVote(dataSet, 0);
		}

		internal static Vote fillVote(DataSet dataSet, Int32 startTableIndex)
		{
			Vote vote = null;

			if (dataSet.Tables[startTableIndex].Rows.Count == 1)
			{
				vote = new Vote();
				vote.applicationName = dataSet.Tables[startTableIndex].Rows[0]["ApplicationName"].ToString();
				vote.componentID = dataSet.Tables[startTableIndex].Rows[0]["ComponentID"].ToString();
				vote.relationID = dataSet.Tables[startTableIndex].Rows[0]["RelationID"].ToString();
				vote.question = dataSet.Tables[startTableIndex].Rows[0]["Question"].ToString();
				vote.isOpen = (Boolean)dataSet.Tables[startTableIndex].Rows[0]["IsOpen"];
				vote.isMultiSelection = (Boolean)dataSet.Tables[startTableIndex].Rows[0]["IsMultiSelection"];
				vote.beginDate = (DateTime)dataSet.Tables[startTableIndex].Rows[0]["BeginDate"];
				vote.endDate = (DateTime)dataSet.Tables[startTableIndex].Rows[0]["EndDate"];
				vote.totalVoteCount = dataSet.Tables[startTableIndex + 2].Select().Length;

				DataRow[] allVoterRows = dataSet.Tables[startTableIndex + 3].Select();

				VoterCollection allVoterCollection = null;

				if (allVoterRows.Length > 0)
				{
					allVoterCollection = new VoterCollection();

					foreach (DataRow allVoterRow in allVoterRows)
					{
						allVoterCollection.Add(
							new Voter(
							allVoterRow["ProviderUserKey"].ToString(),
							allVoterRow["UserName"].ToString(),
							(DateTime)allVoterRow["VoteDate"])
						);
					}
				}

				vote.allVoterCollection = allVoterCollection;
				vote.totalVoterCount = allVoterRows.Length;


				VoteAnswerCollection answerCollection = null;
				if (dataSet.Tables[startTableIndex + 1].Rows.Count > 0)
				{
					answerCollection = new VoteAnswerCollection();

					VoteAnswer answer = null;

					foreach (DataRow row in dataSet.Tables[startTableIndex + 1].Rows)
					{
						DataRow[] answerVoterRows = dataSet.Tables[startTableIndex + 2].Select(String.Format("AnswerID = '{0}'", row["AnswerID"].ToString()));

						VoterCollection answerVoterCollection = null;

						if (vote.IsOpen)
						{
							if (answerVoterRows.Length > 0)
							{
								answerVoterCollection = new VoterCollection();

								foreach (DataRow answerVoterRow in answerVoterRows)
								{
									answerVoterCollection.Add(
										new Voter(
										answerVoterRow["ProviderUserKey"].ToString(),
										answerVoterRow["UserName"].ToString(),
										(DateTime)answerVoterRow["VoteDate"])
									);
								}
							}
						}

						answer = new VoteAnswer(
							row["RelationID"].ToString(),
							row["AnswerID"].ToString(),
							row["Answer"].ToString(),
							(Int32)row["Index"],
							answerVoterRows.Length,
							answerVoterCollection,
							vote.TotalVoteCount > 0 ? (Convert.ToDecimal(answerVoterRows.Length) / vote.TotalVoteCount) * 100 : 0);

						answerCollection.Add(answer);
					}

					vote.answerCollection = answerCollection;
				}
			}

			return vote;
		}
	}

	public class VoteAnswer
	{
		private VoteAnswer() { }

		internal VoteAnswer(String relationID, String answerID, String answer, Int32 index, Int32 voteCount, VoterCollection voterCollection, Decimal percentage)
		{
			this.relationID = relationID;
			this.answerID = answerID;
			this.answer = answer;
			this.index = index;
			this.voteCount = voteCount;
			this.voterCollection = voterCollection;
			this.percentage = percentage;
		}

		private String relationID;
		public String RelationID { get { return this.relationID; } }

		private String answerID;
		public String AnswerID { get { return this.answerID; } }

		private String answer;
		public String Answer { get { return this.answer; } }

		private Int32 index;
		public Int32 Index { get { return this.index; } }

		private Int32 voteCount;
		public Int32 VoteCount { get { return this.voteCount; } }

		private VoterCollection voterCollection;
		public VoterCollection VoterCollection { get { return this.voterCollection; } }

		private Decimal percentage;
		public Decimal Percentage { get { return this.percentage; } }
	}

	public class VoteAnswerCollection : System.Collections.ObjectModel.Collection<VoteAnswer> { }

	public class Voter
	{
		private Voter() { }

		internal Voter(String providerUserKey, String userName, DateTime voteDate)
		{
			this.providerUserKey = providerUserKey;
			this.userName = userName;
			this.voteDate = voteDate;
		}

		private String providerUserKey;
		public String ProviderUserKey { get { return this.providerUserKey; } }

		private String userName;
		public String UserName { get { return this.userName; } }

		private DateTime voteDate;
		public DateTime VoteDate { get { return this.voteDate; } }
	}

	public class VoterCollection : System.Collections.ObjectModel.Collection<Voter> { }
}
